# coding=utf-8
# 社交连接api模块

import os
from urllib2 import HTTPError

import web
from web.utils import storage

import utils
import settings
import redirect
from weibo import APIClient, APIError
from qqweibo import OAuthHandler, API, JSONParser, ModelParser

__all__ = ('SC', 'SinaClient', 'TencentClient')

DEFAULT_CALLBACK_URL = '/login'
# 有效的社交连接客户端类型
valid_clients = ('Sina', 'Tencent', )

class SC(object):
    '社交连接管理器'

    def __init__(self):
        self._clients = {}

    def get(self, key, callback_url = DEFAULT_CALLBACK_URL):
        '获取指定的社交连接客户端API'

        # 给定社交连接对象名称不在有效对象列表中
        if key not in valid_clients:
            return False

        if key not in self._clients:
            self._clients[key] = self._create(key, callback_url)
        
        return self._clients[key]


    def __getattr__(self, key):
        '按属性获取社交连接客户端API'
        return self.get(key)

    def _create(self, name, callback_url):
        '创建指定名称的社交链接对象实例'

        # 给定社交连接对象名称不在有效对象列表中
        if name not in valid_clients:
            return False

        # 导入模块
        #module = __import__('controllers.users.sc')
        module = __import__('controllers.users.sc', globals(), locals(), ['%sClient' % name])
        # 获取类对象
        client = getattr( module, '%sClient' % name )
        # 返回实例化对象
        return client(callback_url)


# TODO: 新浪微博客户端
class SinaClient(object):
    '新浪微博客户端'
    name = 'Sina'
    # 显示文字
    text = '新浪微博'
    # 分享按钮
    share_button = '%s/images/common/sina_share_buton.gif' % settings.CONFIG.STATIC_PREFIX
    def __init__(self, callback_url = DEFAULT_CALLBACK_URL):
        self._client = False
        self.callback_url = callback_url
        # 用户在新浪微博的授权令牌会话
        # self.token 是一个json，
        # 包含：
        #       access_token:   接口实际需要得授权码
        #       expires_in:     授权超时时间
        #       uid:            用户id
        #       以上参数均是 string 类型
        self.token = False

    def _create_client(self):
        '创建新浪微博客户端'
        if not self._client:
            self._client = APIClient(settings.SINA_APP_KEY,
                    settings.SINA_APP_SECRET, 
                    redirect.combine(
                            utils.format_absurl(self.callback_url),
                            sc = SinaClient.name
                        )
                    )
        return self._client

    client = property(_create_client)

    def get_authorize_url(self):
        '获取登录认证地址'
        return '%s&with_offical_account=1' % self.client.get_authorize_url()

    def request_access_token(self, code):
        return self.client.request_access_token(code)

    def get_response_code(self, input):
        """获取OAuth响应关键参数"""
        return input.get('code', None)

    def set_access_token(self, token = False):
        if not token:
            token = self.token
        return self.client.set_access_token(token.access_token
                                            , long(token.expires_in))

    def get_user_show(self, uid):
        try:
            usr = self.client.get.users__show(uid = uid)
        except HTTPError, e:
            # 暂时抛出异常
            raise
        if usr:
            # 将用户信息组合为统一的格式
            return storage(screen_name = usr.screen_name
                    , avatar = usr.profile_image_url
                    , gender = usr.gender
                    , domain = usr.domain
                    , uid = uid)
        return False

    def update(self, status):
        '发布一条新微博'
        self.set_access_token()
        try:
            return self.client.post.statuses__update(status = status)
        except HTTPError, e:
            return False

    def upload_media_status(self, status, media_id):
        """
        发布一条微博信息同时携带一张媒体图
        """
        if media_id > 0:
            #pic = os.path.join(settings.APP_DIR, utils.get_media_url(media_id)[1:])
            pic = utils.get_media_path(media_id)
            if pic and os.path.exists(pic):
                # 路径有效且存在
                return self.upload_status(status, pic)
        # 在没有图片或图片无效的情况下
        # 只发送文字微博
        return self.update(status)

    def upload_status(self, status, pic):
        '上传图片并发布一条微博信息'
        #pic = '%s/static/test/images/%s' % (settings.APP_DIR, pic)
        pic_data = open(pic, 'rb')
        self.set_access_token()
        try:
            return self.client.upload.statuses__upload(status = status, pic = pic_data)
        except HTTPError, e:
            return False
        finally:
            pic_data.close()

    def upload_url_status(self, status, url):
        '指定一个图片URL地址抓取后上传并同时发布一条新微博'
        self.set_access_token()
        try:
            return self.client.upload.statuses__upload_url_text(
                        status = status
                        , url = url)
        except HTTPError, e:
            return False

    def follow(self, uid = settings.SINA_ID):
        '关注指定用户'
        self.set_access_token()
        try:
            return self.client.post.friendships__create(uid = uid)
        except HTTPError, e:
            raise

    def followed(self, source_uid = False, target_uid = settings.SINA_ID):
        '给定用户（source_uid）是否已经关注了目标用户（target_uid）'
        # 获取两者关注关系
        # 如果未指定源用户，则为当前登录用户
        if not source_uid:
            source_uid = self.token.uid

        r = self.follow_show(source_uid, target_uid)
        if r:
            # 返回目标用户是否已被关注
            return r.target.followed_by
        # 如果获取失败，则认为未关注
        return False

    def follow_show(self, source_uid = False, target_uid = settings.SINA_ID):
        '获取指定两个用户的关注关系'
        # 如果未指定源用户，则为当前登录用户
        if not source_uid:
            source_uid = self.token.uid

        self.set_access_token()
        try:
            return self.client.get.friendships__show(source_id = source_uid
                                                , target_id = target_uid)
        except HTTPError, e:
            raise

    # TODO:
    # @session as UserSession 用户会话对象
    def logined(self):
        '判定指定会话是否已通过认证'
        return self.token and self.token.access_token and self.token.uid > 0


    # TODO:
    # @uid as long 用户在寻宝图上的内码
    # @token as Object 用户在社交连接成功后，需要保存的数据
    def save_cookie(self, uid, token):
        '存入特定格式的cookie内容'
        # 核心cookie，security user 缩写
        web.setcookie('su'
                , utils.encrypt('%s|%s|%s|%s|%s' % (uid
                                    , self.name
                                    , token.access_token
                                    , token.expires_in
                                    , token.uid) ))

    # TODO:
    # @cookie_vals as [] cookie中加密部分（su）的数据
    def resolve_cookie(self, cookie_vals):
        '提取cookie中的数据'
        if len(cookie_vals) == 5:
            self.token = storage(access_token = cookie_vals[2]
                                        , expires_in = cookie_vals[3]
                                        , uid = cookie_vals[4])

# TODO: 腾讯微博客户端
class TencentClient(object):
    '腾讯微博客户端'
    name = 'Tencent'
    # 显示文本
    text = '腾讯微博'
    # 分享按钮
    share_button = '%s/images/common/tencent_share_button.png' % settings.CONFIG.STATIC_PREFIX
    def __init__(self, callback_url = DEFAULT_CALLBACK_URL):
        self._json_api = False
        self._model_api = False
        self._client = False
        self.callback_url = callback_url
        # 用户在腾讯微博的授权令牌会话
        # self.token 是一个json，
        # 包含：
        #       key:            安全key
        #       secret:         安全密匙
        #       uid:            用户id
        #       以上参数均是 string 类型
        self.token = False

    def _create_client(self):
        '创建腾讯微博客户端'
        if not self._client:
            self._client = OAuthHandler(settings.QQ_APP_KEY
                    , settings.QQ_APP_SECRET
                    , callback = redirect.combine(
                            utils.format_absurl(self.callback_url),
                            sc = TencentClient.name
                        )
                    )
        return self._client

    client = property(_create_client)

    def _create_json_api(self):
        if not self._json_api:
            self._json_api = API(self.client, parser = JSONParser())
        return self._json_api

    json_api = property(_create_json_api)

    def _create_model_api(self):
        if not self._model_api:
            self._model_api = API(self.client, parser = ModelParser())
        return self._model_api

    model_api = property(_create_model_api)

    def get_authorize_url(self):
        '获取腾讯微博的授权地址'
        # 获取授权地址
        url = self.client.get_authorization_url()
        # get_authorization_url方法中初始化了request_token
        # 我们需要保存下来
        # 因为里面保存了，后面要使用的签名数据
        self._save_request_token()
        # 返回url
        return url

    def _save_request_token(self):
        """
        设置OAuth实例的request_token
        因为在callback时，你不能在获取到它了
        该方法通常只在get_authorize_url方法中调用一次即可
        """
        web.setcookie('_qs', self.client.request_token.secret)

    def _set_request_token(self, code):
        self.client.set_request_token(code.oauth_token
                , web.cookies().get('_qs', None))
        # 清除临时数据
        web.setcookie('_qs', None)

    def request_access_token(self, code):
        # 这里重设 oAuth 实例的request_token
        self._set_request_token(code)
        # 获取后面需要保存的 key 和 secret
        t = self.client.get_access_token(int(code.oauth_verifier))
        # 构造一个统一包含uid属性的token
        return storage(key = t.key
                    , secret = t.secret
                    , uid = code.openid)

    def get_response_code(self, input):
        """获取OAuth响应关键参数"""
        return storage(oauth_token = input.get('oauth_token', None)
                        , oauth_verifier = input.get('oauth_verifier', None)
                        , openid = input.get('openid', None)
                        , openkey = input.get('openkey', None))

    def set_access_token(self, token = False):
        if not token:
            token = self.token
        return self.client.setToken(token.key, token.secret)

    def get_user_show(self, uid):
        try:
            usr = self.json_api.me()['data']
        except HTTPError, e:
            # 暂时抛出异常
            raise
        if usr:
            # 将用户信息组合为统一的格式
            return storage(screen_name = usr['nick']
                    , avatar = '%s/100' % usr['head']
                    , gender = usr['sex']
                    , domain = usr['name']
                    , uid = uid)
        return False

    def update(self, status):
        '发布一条新微博'
        self.set_access_token()
        try:
            return self.model_api.tweet.add(status.encode('utf-8')
                        , clientip = utils.get_client_ip())
        except HTTPError, e:
            return False

    def upload_media_status(self, status, media_id):
        """
        发布一条微博信息同时携带一张媒体图
        """
        if media_id > 0:
            #pic = os.path.join(settings.APP_DIR, utils.get_media_url(media_id)[1:])
            pic = utils.get_media_path(media_id)
            if pic and os.path.exists(pic):
                # 路径有效且存在
                return self.upload_status(status, pic)

        # 在没有图片或图片无效的情况下
        # 只发送文字微博
        return self.update(status)

    def upload_status(self, status, pic):
        '指定一个文件上传，发布一篇图片微博'
        self.set_access_token()
        try:
            return self.model_api.tweet.addpic(pic
                    , status.encode('utf-8')
                    , utils.get_client_ip())
        except HTTPError, e:
            return False

    def upload_url_status(self, status, url):
        '指定一个图片URL地址抓取后上传并同时发布一条新微博'
        self.set_access_token()
        try:
            return self.model_api.tweet.add_pic_url(status.encode('utf-8')
                    , client = utils.get_client_ip()
                    , pic_url = url)
        except HTTPError, e:
            return False

    def follow(self, uid = settings.SINA_ID):
        pass
    
    def followed(self, source_uid = False, target_uid = settings.SINA_ID):
        pass
    
    def follow_show(self, source_uid = False, target_uid = settings.SINA_ID):
        pass

    # TODO:
    def logined(self):
        '判定指定会话是否已通过认证'
        return self.token and self.token.key and self.token.uid > 0

    # TODO:
    # @uid as long 用户在寻宝图上的内码
    # @token as Object 用户在社交连接成功后，需要保存的数据
    def save_cookie(self, uid, token):
        '存入特定格式的cookie内容'
        # 核心cookie，security user 缩写
        web.setcookie('su'
                , utils.encrypt('%s|%s|%s|%s|%s' % (uid
                                    , self.name
                                    , token.key
                                    , token.secret
                                    , token.uid) ))

    # TODO:
    # @cookie_vals as [] cookie中加密部分（su）的数据
    def resolve_cookie(self, cookie_vals):
        '提取cookie中的数据'
        if len(cookie_vals) == 5:
            self.token = storage(key = cookie_vals[2]
                                        , secret = cookie_vals[3]
                                        , uid = cookie_vals[4])
